/**
 * @file simplechatserver.cpp
 * @author Suk Robert Koh (srkoh87z@gmail.com)
 *
 * @brief Simple chat server/client program using NGine
 */

#include <iostream>
#include <time.h>
#include "ngine/server/ngineserver.h"

using namespace std;

// list of types of messages and protocol
#define MSG_TYPE_JOIN				1		// [user's name]
#define MSG_TYPE_LIST				2		// [peer's name]|[peer's name]|...|[peer's name]
#define MSG_TYPE_NOTICE         	10      // [notice message]
#define MSG_TYPE_CHAT_NORMAL		11		// [chatter's name]|[chat message]
#define MSG_TYPE_CHAT_WHISPER_TO	12      // [target's name]|[chat message]
#define MSG_TYPE_CHAT_WHISPER_FROM	13      // [sender's name]|[chat message]

#define MAX_USER    64		// maximum number of users allowed to connect at once
#define PORT		5000	// port number to use

// User structure to store connected user's information
typedef struct _User
{
    bool connected;
    unsigned long userId;
    string name;
} User;

User users[MAX_USER];

// function prototypes
void handleMessage(NGMessage *msg);
void sendUserList();

int main()
{
	// initialize user list
	for(int i = 0; i < MAX_USER; i++)
	{
		users[i].connected = FALSE;
		users[i].userId = 0;
		users[i].name = "";
	}

	// initialize NGine
	NGLogger::info("Initializing NGine...");
	NGineServer::init(PORT, MAX_USER, 0);
	NGLogger::info("NGine initialized!\n");

	double timeElapsed = 0, timeNow = NGUtils::currentTime();

	while(1)
	{
		timeElapsed = NGUtils::currentTime() - timeNow;

		if(timeElapsed >= 5)
		{
			sendUserList();
			timeElapsed = 0;
			timeNow = NGUtils::currentTime();
		}

		// if there is anything in message queue
		if(NGineServer::getQueueSize() > 0)
		{
			NGMessage *msg = NGineServer::getFirstMessage();

			// call a function to handle message
			handleMessage(msg);
		}
	}

	return 0;
}

// handles received messages
void handleMessage(NGMessage *msg)
{
	// retrieve information
	unsigned long senderId = msg->getSenderId();
	unsigned char type = msg->getType();
	string data = msg->getData();

	User *user = 0;
	string messageToSend, targetName;

	// switch on type
	switch(type)
	{
	case NGINE_MSG_CONNECT:
		break;
	case NGINE_MSG_DISCONNECT:
		for(int i = 0; i < MAX_USER; i++)
		{
			if(users[i].connected && users[i].userId == senderId)
			{
				messageToSend = " * " + users[i].name + " has left the server.";
				NGineServer::sendMessageAll(MSG_TYPE_NOTICE, messageToSend.c_str());
				users[i].connected = false;
				users[i].name = "";
				users[i].userId = 0;

				// update everyone's user list
				sendUserList();
			}
		}
		break;
	// new user is attempting to join
	case MSG_TYPE_JOIN:
		// check if another user is connected using same name, also while on it search for a empty user space
		for(int i = 0; i < MAX_USER; i++)
		{
			if(user == 0 && !users[i].connected)
				user = &users[i];

			if(users[i].connected && !data.compare(users[i].name))
			{
				// user already exists, send error message
				NGineServer::sendMessage(MSG_TYPE_JOIN, "EXISTS", senderId);
				goto cleanUp;
			}
		}

		// name is good for use!
		if(user != 0)
		{
			user->connected = true;
			user->name = data;
			user->userId = senderId;

			// send success message
			NGineServer::sendMessage(MSG_TYPE_JOIN, "SUCCESS", senderId);

			// send a welcome message
			messageToSend = " * Welcome to the SimpleChat server " + data + ".";
			NGineServer::sendMessage(MSG_TYPE_NOTICE, messageToSend.c_str(), senderId);

			// let everyone else know a user joined
			messageToSend = " * " + data + " has joined the server.";
			NGineServer::sendMessageAll(MSG_TYPE_NOTICE, messageToSend.c_str(), senderId);

			// update everyone's user list
			sendUserList();
		}
		break;
	case MSG_TYPE_CHAT_NORMAL:
		// find the user
		for(int i = 0; i < MAX_USER; i++)
		{
			if(users[i].connected && users[i].userId == senderId)
			{
				// create a message to send and send to all
				messageToSend = users[i].name + "|" + data;
				NGineServer::sendMessageAll(MSG_TYPE_CHAT_NORMAL, messageToSend.c_str());
				break;
			}
		}
		break;
	case MSG_TYPE_CHAT_WHISPER_TO:
		// retrieve target's name
		targetName = data.substr(0, data.find('|'));
		if(!targetName.compare(data))
			goto cleanUp;
		data = data.substr(data.find('|') + 1);

		// find the user with target name
		for(int i = 0; i < MAX_USER; i++)
		{
			if(users[i].connected && !users[i].name.compare(targetName))
			{
				// find sender's name
				for(int j = 0; j < MAX_USER; j++)
				{
					if(users[j].connected && users[j].userId == senderId)
					{
						messageToSend = users[j].name + "|" + data;
						NGineServer::sendMessage(MSG_TYPE_CHAT_WHISPER_FROM, messageToSend.c_str(), users[i].userId);
						messageToSend = users[i].name + "|" + data;
						NGineServer::sendMessage(MSG_TYPE_CHAT_WHISPER_TO, messageToSend.c_str(), users[j].userId);
						goto cleanUp;
					}
				}
			}
		}
		break;
	default:
		NGLogger::error("Invalid message type received: %d", type);
	}

cleanUp:
	// need to delete message once finished
	// this will no longer be needed once memory manager is implemented
	delete msg;
}

void sendUserList()
{
	string userList = "";

	for(int i = 0; i < MAX_USER; i++)
		if(users[i].connected)
			userList += users[i].name + "|";

	NGineServer::sendMessageAll(MSG_TYPE_LIST, userList.c_str());
}
